package cn.lg.soar.mvc.log;

import cn.lg.soar.common.constant.MDCKey;
import cn.lg.soar.common.model.LogDTO;
import cn.lg.soar.common.util.ExceptionUtil;
import cn.lg.soar.common.util.IpUtil;
import cn.lg.soar.common.util.current.ICurrentUser;
import cn.lg.soar.common.util.current.ThreadContext;
import cn.lg.soar.common.util.current.UserContext;
import cn.lg.soar.core.aspect.AopEvaluationContext;
import cn.lg.soar.core.aspect.SpelExpressionUtils;
import cn.lg.soar.core.handler.log.ILogCollector;
import cn.lg.soar.core.util.InnerSerializeUtils;
import cn.lg.soar.mvc.enums.LogErrorModeEnum;
import cn.lg.soar.mvc.util.ServletUtils;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import jakarta.servlet.http.HttpServletRequest;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

public class OperationLogInterceptor implements MethodInterceptor {

    @Autowired
    private ILogCollector collector;
    @Autowired
    private ILogConfigService configService;

    private final static LogConfigDTO DEF = new LogConfigDTO();
    private Cache<String, LogConfigDTO> cache = Caffeine.newBuilder()
            .maximumSize(2048)
            .initialCapacity(32)
            .expireAfterWrite(30, TimeUnit.SECONDS)
            .build();

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        String resource = ThreadContext.getResource();
        if (resource == null) {
            return invocation.proceed();
        }
        LogConfigDTO config = cache.get(resource, c -> {
            LogConfigDTO logConfig = configService.getLogConfig(c);
            return Objects.requireNonNullElse(logConfig, DEF);
        });
        if (config == DEF) {
            return invocation.proceed();
        }
        LocalDateTime startTime = LocalDateTime.now();
        Object result;
        try {
            result = invocation.proceed();
        } catch (RuntimeException e) {
            save(invocation, config, startTime, e, resource);
            throw e;
        }
        save(invocation, config, startTime, result, resource);
        return result;
    }

        protected void save(MethodInvocation invocation, LogConfigDTO config, LocalDateTime startTime, Object result, String resource) {
            Method method = invocation.getMethod();
            Object[] args = invocation.getArguments();
            LogDTO log = new LogDTO();
            log.setType(config.getType());
            log.setTitle(config.getName());
            Object object;
            if (!"".equals(config.getContent())) {
                AopEvaluationContext context = new AopEvaluationContext(method, args, result);
                object = SpelExpressionUtils.getValue(config.getContent(), context);
                if (object != null) {
                    log.setContent(object.toString());
                }
            }
            log.setStartTimeAndDuration(startTime);
            log.setTenantId(ThreadContext.getTenantId());
            // 用户相关
            ICurrentUser user = UserContext.getUserNullable();
            if (user == null) {
                log.setUserId(0L);
                log.setUserType(0);
                log.setClientType(0);
            } else {
                log.setUserId(user.getId());
                log.setUserType(user.getUserType());
                log.setClientType(user.getClientType());
            }
            // 请求相关
            HttpServletRequest request = ServletUtils.getRequest();
            log.setTraceId(MDC.get(MDCKey.TRACE_ID));
            log.setClientIp(IpUtil.getRemoteIp(request));
            log.setPath(resource);
            log.setUserAgent(request.getHeader("User-Agent"));
            // java相关
            String className = method.getDeclaringClass().getName();
            String methodName = method.getName();
            log.setJavaMethod(className + "#" + methodName);
            switch (config.getArgsMode()) {
                case el -> {
                    AopEvaluationContext context = new AopEvaluationContext(method, args);
                    object = SpelExpressionUtils.getValue(config.getArgsEl(), context);
                    log.setArgs(InnerSerializeUtils.toJson(object));
                }
                case index -> {
                    object = args[config.getArgsIndex()];
                    log.setArgs(InnerSerializeUtils.toJson(object));
                }
                case args -> {
                    log.setArgs(InnerSerializeUtils.toJson(args));
                }
                default -> {}
            }
            switch (config.getResultMode()) {
                case el -> {
                    EvaluationContext context = new StandardEvaluationContext(result);
                    object = SpelExpressionUtils.getValue(config.getResultEl(), context);
                    log.setResult(InnerSerializeUtils.toJson(object));
                }
                case raw -> {
                    log.setResult(InnerSerializeUtils.toJson(result));
                }
                default -> {}
            }
            // 保存
            collector.add(log);
        }

        protected void save(MethodInvocation invocation, LogConfigDTO annotation, LocalDateTime startTime, Throwable throwable, String resource) {
            if (annotation.getErrorMode() == LogErrorModeEnum.not) {
                return;
            }
            Method method = invocation.getMethod();
            Object[] args = invocation.getArguments();
            LogDTO log = new LogDTO();
            log.setType(annotation.getType());
            log.setTitle(annotation.getName());

            Object object;
            if (!"".equals(annotation.getContent())) {
                AopEvaluationContext context = new AopEvaluationContext(method, args);
                object = SpelExpressionUtils.getValue(annotation.getContent(), context);
                if (object != null) {
                    log.setContent(object.toString());
                }
            }
            log.setStartTimeAndDuration(startTime);
            log.setTenantId(ThreadContext.getTenantId());
            // 用户相关
            ICurrentUser user = UserContext.getUserNullable();
            if (user == null) {
                log.setUserId(0L);
                log.setUserType(0);
                log.setClientType(0);
            } else {
                log.setUserId(user.getId());
                log.setUserType(user.getUserType());
                log.setClientType(user.getClientType());
            }
            // 请求相关
            HttpServletRequest request = ServletUtils.getRequest();
            log.setTraceId(MDC.get(MDCKey.TRACE_ID));
            log.setClientIp(IpUtil.getRemoteIp(request));
            log.setPath(resource);
            log.setUserAgent(request.getHeader("User-Agent"));
            // java相关
            String className = method.getDeclaringClass().getName();
            String methodName = method.getName();
            log.setJavaMethod(className + "#" + methodName);
            switch (annotation.getArgsMode()) {
                case el -> {
                    AopEvaluationContext context = new AopEvaluationContext(method, args);
                    object = SpelExpressionUtils.getValue(annotation.getArgsEl(), context);
                    log.setArgs(InnerSerializeUtils.toJson(object));
                }
                case index -> {
                    object = args[annotation.getArgsIndex()];
                    log.setArgs(InnerSerializeUtils.toJson(object));
                }
                case args -> {
                    log.setArgs(InnerSerializeUtils.toJson(args));
                }
                default -> {}
            }
            switch (annotation.getErrorMode()) {
                case stack -> {
                    log.setErrorStack(ExceptionUtil.getRefinedStackTrace(throwable, 20));
                }
                case msg -> {
                    log.setErrorStack(throwable.getMessage());
                }
                default -> {}
            }
            // 保存
            collector.add(log);
        }
        
    }